export type JSONSchema = {
    $schema?: string;
    description?: string;
    mock?: string;
} & (
    {
        type: "string" | "number" | "boolean";
    } | {
        type: "object";
        properties: Record<string, JSONSchema>;
    } | {
        type: "array";
        items: JSONSchema;
    }
)

export const _ = {
    replace: function replace <T extends object = any>(obj: T, keypath: string[], field1: string, field2: string, value?: any) {
        if (!obj) {
            return obj
        }
        const entries = Object.entries(obj)
        let result = {} as T
        if (keypath.length === 0) {
            entries.forEach(([key, v]) => {
                if (key === field1) {
                    result[field2] = value || v
                } else {
                    result[key] = v
                }
            })
        } else {
            const [first, ...rest] = keypath
            result = {
                ...obj,
                [first]: replace(obj[first], rest, field1, field2, value),
            }
        }
        return result
    },
    set: function set <T extends object = any>(obj: T, keypath: string[], value?: any, immutable = false) {
        if (!obj || keypath.length === 0) {
            return obj
        }
        const result = immutable ? { ...obj } : obj
        if (keypath.length === 1) {
            if (typeof value === 'undefined') {
                delete result[keypath[0]]
            } else {
                Object.assign(result, {
                    [keypath[0]]: value
                })
            }
        } else {
            const [key, ...rest] = keypath
            if (!(key in result)) {
                result[key] = set({}, rest, value, immutable)
            } else {
                result[key] = set(result[key], rest, value, immutable)
            }
        }
        return result
    },
    get: function get (obj: any, keypath: string[]) {
        if (!obj || keypath.length === 0) {
            return obj
        }
        if (keypath.length === 1) {
            return obj[keypath[0]]
        } else {
            const [key, ...rest] = keypath
            if (key in obj) {
                return get(obj[key], rest)
            }
            return undefined
        }
    }
}

export const FieldTypes: JSONSchema["type"][] = [
    "string",
    "number",
    "object",
    "array",
    "boolean",
]

export interface JSONSchemaLine {
    keypath: string[];
    isLeaf: boolean;
    isClose: boolean;
    hidden: boolean;
    deepth: number;
    autoName: boolean;
    field: string;
    schema: JSONSchema;
}

export const flatJSONSchema = function flat (schema: JSONSchema, closeKeys = new Set<string>(), keypath = ['root'], deepth = 0, hidden = false, autoName = true) {
    const lines: JSONSchemaLine[] = []
    const key = keypath.join(',')
    const isLeaf = !(schema.type === 'array' || schema.type === 'object')
    const isClose = closeKeys.has(key)
    const field = keypath[keypath.length - 1]
    const line: JSONSchemaLine = {
        keypath,
        isLeaf,
        isClose,
        deepth,
        autoName,
        field,
        schema,
        hidden,
    }
    lines.push(line)
    switch (schema.type) {
        case 'array':
            flat(schema.items, closeKeys, [...keypath, 'items'], deepth + 1, hidden || isClose, true)
            .forEach(line => lines.push(line))
            break;
        case 'object':
            Object.entries(schema.properties).forEach(function ([k, v]) {
                flat(v, closeKeys, [...keypath, 'properties', k], deepth + 1, hidden || isClose, false)
                .forEach(line => lines.push(line))
            })
            break;
    }

    return lines
}

export const addField = function (schema: JSONSchema, keypath: string[]) {
    const obj = _.get(schema, keypath)
    let index = 1
    while (`field_${index}` in obj) index++
    const result = _.set(schema, [...keypath, `field_${index}`], {
        type: 'string',
    }, true)
    return result
}